package besta.moon;

import java.nio.channels.FileChannel;
import java.sql.Array;
import java.sql.Blob;
import java.sql.CallableStatement;
import java.sql.Clob;
import java.sql.Connection;
import java.sql.DatabaseMetaData;
import java.sql.NClob;
import java.sql.PreparedStatement;
import java.sql.SQLClientInfoException;
import java.sql.SQLException;
import java.sql.SQLWarning;
import java.sql.SQLXML;
import java.sql.Savepoint;
import java.sql.Statement;
import java.sql.Struct;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.concurrent.Executor;

import besta.moon.metadata.MoonDatabaseMetaData;
import besta.moon.statement.MoonCallableStatement;
import besta.moon.statement.MoonStatement;
import besta.moon.statement.MoonPreparedStatement;
import besta.moon.statement.MoonSavepoint;
import besta.moon.engine.Database;
import besta.moon.engine.TransactionStep;

public class MoonConnection implements Connection {

    private boolean readOnly;
    private Database database;
    private boolean autoCommit = true;
    public int isolationLevel = TRANSACTION_READ_COMMITTED;
    private List<TransactionStep> commitPages = new ArrayList<>();
    private long transactionTime;
    private final MoonDatabaseMetaData metadata;
    private int holdability;
    public final MoonLogger log;

    public MoonConnection(Properties props) throws SQLException {
        MoonException.setLanguage(props.get("locale"));
        log = new MoonLogger();
        String name = props.getProperty("dbpath");
        readOnly = "true".equals(props.getProperty("readonly"));
        boolean create = "true".equals(props.getProperty("create"));
        database = Database.getDatabase(name, this, create);
        metadata = new MoonDatabaseMetaData(this);
    }

    public MoonConnection(MoonConnection con) {
        readOnly = con.readOnly;
        database = con.database;
        metadata = con.metadata;
        log = con.log;
    }

    public Database getDatabase(boolean returnNull) throws SQLException {
        testClosedConnection();
        if (!returnNull && database == null) {
            throw MoonException.create(MoonCode.DB_NOTCONNECTED);
        }
        return database;
    }

    public Object getMonitor() {
        return this;
    }

    @Override
    public Statement createStatement() throws SQLException {
        return new MoonStatement(this);
    }

    @Override
    public PreparedStatement prepareStatement(String sql) throws SQLException {
        return new MoonPreparedStatement(this, sql);
    }

    @Override
    public CallableStatement prepareCall(String sql) throws SQLException {
        return new MoonCallableStatement(this, sql);
    }

    @Override
    public String nativeSQL(String sql) {
        return sql;
    }

    @Override
    public void setAutoCommit(boolean autoCommit) throws SQLException {
        if (log.isLogging()) {
            log.println("AutoCommit:" + autoCommit);
        }
        if (this.autoCommit != autoCommit) {
            commit();
            this.autoCommit = autoCommit;
        }
    }

    @Override
    public boolean getAutoCommit() {
        return autoCommit;
    }

    public void add(TransactionStep storePage) throws SQLException {
        testClosedConnection();
        synchronized (getMonitor()) {
            commitPages.add(storePage);
        }
    }

    @Override
    public void commit() throws SQLException {
        log.println("Commit");
        testClosedConnection();
        synchronized (getMonitor()) {
            try {
                int count = commitPages.size();
                for (int i = 0; i < count; i++) {
                    TransactionStep page = (TransactionStep) commitPages.get(i);
                    page.commit();
                }
                for (int i = 0; i < count; i++) {
                    TransactionStep page = (TransactionStep) commitPages.get(i);
                    page.freeLock();
                }
                commitPages.clear();
                transactionTime = System.currentTimeMillis();
            } catch (Throwable e) {
                rollback();
                throw MoonException.createFromException(e);
            }
        }
    }

    public void rollbackFile(FileChannel raFile) throws SQLException {
        testClosedConnection();
        synchronized (getMonitor()) {
            for (int i = commitPages.size() - 1; i >= 0; i--) {
                TransactionStep page = (TransactionStep) commitPages.get(i);
                if (page.raFile == raFile) {
                    page.rollback();
                    page.freeLock();
                }
            }
        }
    }

    public void rollback(int savepoint) throws SQLException {
        testClosedConnection();
        synchronized (getMonitor()) {
            for (int i = commitPages.size() - 1; i >= savepoint; i--) {
                TransactionStep page = (TransactionStep) commitPages.remove(i);
                page.rollback();
                page.freeLock();
            }
        }
    }

    @Override
    public void rollback() throws SQLException {
        log.println("Rollback");
        testClosedConnection();
        synchronized (getMonitor()) {
            int count = commitPages.size();
            for (int i = 0; i < count; i++) {
                TransactionStep page = (TransactionStep) commitPages.get(i);
                page.rollback();
                page.freeLock();
            }
            commitPages.clear();
            transactionTime = System.currentTimeMillis();
        }
    }

    @Override
    public void close() throws SQLException {
        rollback();
        database = null;
        commitPages = null;
        Database.closeConnection(this);
    }

    public final void testClosedConnection() throws SQLException {
        if (isClosed()) {
            throw MoonException.create(MoonCode.CONNECTION_CLOSED);
        }
    }

    @Override
    public boolean isClosed() {
        return (commitPages == null);
    }

    @Override
    public DatabaseMetaData getMetaData() {
        return metadata;
    }

    @Override
    public void setReadOnly(boolean readOnly) {
        // TODO Connection ReadOnly implementing
    }

    @Override
    public boolean isReadOnly() {
        return readOnly;
    }

    @Override
    public void setCatalog(String catalog) throws SQLException {
        testClosedConnection();
        database = Database.getDatabase(catalog, this, false);
    }

    @Override
    public String getCatalog() {
        if (database == null) {
            return "";
        }
        return database.getName();
    }

    @Override
    public void setTransactionIsolation(int level) throws SQLException {
        if (!metadata.supportsTransactionIsolationLevel(level)) {
            throw MoonException.create(MoonCode.ISOLATION_UNKNOWN, String.valueOf(level));
        }
        isolationLevel = level;
    }

    @Override
    public int getTransactionIsolation() {
        return isolationLevel;
    }

    @Override
    public SQLWarning getWarnings() {
        return null;
    }

    @Override
    public void clearWarnings() {
       
    }

    @Override
    public Statement createStatement(int resultSetType, int resultSetConcurrency) throws SQLException {
        return new MoonStatement(this, resultSetType, resultSetConcurrency);
    }

    @Override
    public PreparedStatement prepareStatement(String sql, int resultSetType, int resultSetConcurrency)
            throws SQLException {
        return new MoonPreparedStatement(this, sql, resultSetType, resultSetConcurrency);
    }

    @Override
    public CallableStatement prepareCall(String sql, int resultSetType, int resultSetConcurrency) throws SQLException {
        return new MoonCallableStatement(this, sql, resultSetType, resultSetConcurrency);
    }

    @Override
    public Map getTypeMap() {
        return null;
    }

    @Override
    public void setTypeMap(Map map) {
 
    }

    @Override
    public void setHoldability(int holdability) {
        this.holdability = holdability;
    }

    @Override
    public int getHoldability() {
        return holdability;
    }

    public int getSavepoint() throws SQLException {
        testClosedConnection();
        return commitPages.size(); 
    }

    @Override
    public Savepoint setSavepoint() throws SQLException {
        return new MoonSavepoint(getSavepoint(), null, transactionTime);
    }

    @Override
    public Savepoint setSavepoint(String name) throws SQLException {
        return new MoonSavepoint(getSavepoint(), name, transactionTime);
    }

    @Override
    public void rollback(Savepoint savepoint) throws SQLException {
        if (savepoint instanceof MoonSavepoint) {
            if (((MoonSavepoint) savepoint).transactionTime != transactionTime) {
                throw MoonException.create(MoonCode.SAVEPT_INVALID_TRANS);
            }
            rollback(savepoint.getSavepointId());
            return;
        }
        throw MoonException.create(MoonCode.SAVEPT_INVALID_DRIVER, savepoint);
    }

    @Override
    public void releaseSavepoint(Savepoint savepoint) throws SQLException {
        if (savepoint instanceof MoonSavepoint) {
            ((MoonSavepoint) savepoint).transactionTime = 0;
            return;
        }
        throw MoonException.create(MoonCode.SAVEPT_INVALID_DRIVER, new Object[]{savepoint});
    }

    @Override
    public Statement createStatement(int resultSetType, int resultSetConcurrency, int resultSetHoldability)
            throws SQLException {
        return new MoonStatement(this, resultSetType, resultSetConcurrency);
    }

    @Override
    public PreparedStatement prepareStatement(String sql, int resultSetType, int resultSetConcurrency,
            int resultSetHoldability) throws SQLException {
        return new MoonPreparedStatement(this, sql);
    }

    @Override
    public CallableStatement prepareCall(String sql, int resultSetType, int resultSetConcurrency,
            int resultSetHoldability) throws SQLException {
        return new MoonCallableStatement(this, sql, resultSetType, resultSetConcurrency);
    }

    @Override
    public PreparedStatement prepareStatement(String sql, int autoGeneratedKeys) throws SQLException {
        MoonPreparedStatement pr = new MoonPreparedStatement(this, sql);
        pr.setNeedGeneratedKeys(autoGeneratedKeys);
        return pr;
    }

    @Override
    public PreparedStatement prepareStatement(String sql, int[] columnIndexes) throws SQLException {
        MoonPreparedStatement pr = new MoonPreparedStatement(this, sql);
        pr.setNeedGeneratedKeys(columnIndexes);
        return pr;
    }

    @Override
    public PreparedStatement prepareStatement(String sql, String[] columnNames) throws SQLException {
        MoonPreparedStatement pr = new MoonPreparedStatement(this, sql);
        pr.setNeedGeneratedKeys(columnNames);
        return pr;
    }

    @Override
    public <T> T unwrap(Class<T> iface) throws SQLException {
    
        return null;
    }

    @Override
    public boolean isWrapperFor(Class<?> iface) throws SQLException {
  
        return false;
    }

    @Override
    public Clob createClob() throws SQLException {
    
        return null;
    }

    @Override
    public Blob createBlob() throws SQLException {

        return null;
    }

    @Override
    public NClob createNClob() throws SQLException {

        return null;
    }

    @Override
    public SQLXML createSQLXML() throws SQLException {
 
        return null;
    }

    @Override
    public boolean isValid(int timeout) throws SQLException {
 
        return false;
    }

    @Override
    public void setClientInfo(String name, String value) throws SQLClientInfoException {
   

    }

    @Override
    public void setClientInfo(Properties properties) throws SQLClientInfoException {
    
    }

    @Override
    public String getClientInfo(String name) throws SQLException {
        return null;
    }

    @Override
    public Properties getClientInfo() throws SQLException {
        return null;
    }

    @Override
    public Array createArrayOf(String typeName, Object[] elements) throws SQLException {
          return null;
    }

    @Override
    public Struct createStruct(String typeName, Object[] attributes) throws SQLException {
        return null;
    }

    @Override
    public void setSchema(String schema) throws SQLException {

    }

    @Override
    public String getSchema() throws SQLException {
        return null;
    }

    @Override
    public void abort(Executor executor) throws SQLException {

    }

    @Override
    public void setNetworkTimeout(Executor executor, int milliseconds) throws SQLException {

    }

    @Override
    public int getNetworkTimeout() throws SQLException {
        return 0;
    }
}
